Skip to content

feat(input): add macro language — repeat, literals, delays, holds, raw text#37

Merged
wizzomafizzo merged 2 commits into
mainfrom
feat/input-macro-language
Jun 13, 2026
Merged

feat(input): add macro language — repeat, literals, delays, holds, raw text#37
wizzomafizzo merged 2 commits into
mainfrom
feat/input-macro-language

Conversation

@wizzomafizzo

@wizzomafizzo wizzomafizzo commented Jun 13, 2026

Copy link
Copy Markdown
Member

Extends the input macro grammar (input.keyboard, input.gamepad) with new forms inside {} braces, and adds a new input.text raw-mode command.

New grammar inside {}

Form Example Meaning
Repeat {a*5} Type a five times
Repeat special {enter*3} Press Enter three times
Repeat combo {ctrl+c*2} Send Ctrl+C twice
Quoted literal {"hello"*2} Type hello twice; \" escapes a quote
Verb literal {text:hello*2} Same as quoted form
Delay {delay:500} 500 ms pause (pass-through to core emitter)
Press/hold/release {press:a}, {hold:a:500}, {release:a} Pass-through brace tokens
Sigil sugar {_a}, {~a:500}, {^a} Shorthand for press/hold/release

New command: input.text

**input.text:free text here — the entire argument is literal; no {}/* grammar, no adv-args. \n maps to {enter}, \t maps to {tab}. Useful for search strings, URLs, and virtual keyboard input.

Safety caps

  • Per-*N ceiling: 1000ErrInputMacroRepeatTooLarge
  • Total expanded keys per macro: 5000ErrInputMacroTooLong

Both fail the whole command with a clear error; no silent truncation.

Serialization

Command.String() gains an isInputRawCmd case so input.text round-trips correctly — per-character args are concatenated directly without commas.

Summary by CodeRabbit

Release Notes

  • New Features

    • Introduced input.text command for raw text input without macro syntax processing.
    • Enhanced input macro syntax with repeat support ({key*N}), quoted literals, and pass-through verbs (delay, press, release, hold).
    • Added support for keyboard modifiers and sigils (_, ^, ~) in input macros.
  • Tests

    • Added comprehensive test coverage for new input macro and text parsing behaviors.
    • Extended fuzzing and round-trip testing for parser validation.

…w text

Extends the input macro grammar inside `{}` braces:

- Repeat: `{a*5}`, `{enter*3}`, `{ctrl+c*2}`
- Quoted literal: `{"hello world"*2}` — braces-safe, `\"` escapes the quote
- Verb literal: `{text:content*N}` — same semantics as quoted form
- Delay pass-through: `{delay:N}` forwarded to core emitter as a brace token
- Hold/press/release pass-throughs: `{press:k}`, `{release:k}`, `{hold:k:dur}`
- Sigil sugar: `{_k}` (press), `{^k}` (release), `{~k:dur}` (hold)
- New command `input.text`: raw mode — no `{}`/`*` grammar, no adv-args, `\n`→`{enter}`, `\t`→`{tab}`

Safety caps: per-repeat max 1000, total expanded keys per macro max 5000;
both fail the command with a clear error rather than silently truncating.

`Command.String()` gains an `isInputRawCmd` case so `input.text` args
round-trip correctly (chars concatenated directly, no commas).
@coderabbitai

coderabbitai Bot commented Jun 13, 2026

Copy link
Copy Markdown

Review Change Stack

Warning

Review limit reached

@wizzomafizzo, we couldn't start this review because you've reached your PR review rate limit.

More reviews will be available in 2 hours, 15 minutes, and 3 seconds. Learn how PR review limits work.

Your organization has used up its prepaid credits, and credit purchases are no longer available. Enable the review add-on in the billing tab to keep reviews running — you're only billed for reviews past your plan's rate limits ($0.25/file).

⌛ How to resolve this issue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

We recommend that you space out your commits to avoid hitting the rate limit.

🚦 How do rate limits work?

CodeRabbit enforces hourly rate limits for each developer per organization.

Our paid plans include higher PR review limits than trial, open-source, and free plans. In all cases, reviews become available again over time. During sustained high-volume PR review activity, CodeRabbit may temporarily slow when the next review becomes available.

Please see our Fair Usage Limits Policy for further information.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: cf481a16-6318-4cbd-a9dd-1098a2612449

📥 Commits

Reviewing files that changed from the base of the PR and between 0e38c44 and b70a5d5.

📒 Files selected for processing (3)
  • arguments.go
  • parser_input_macro_test.go
  • reader.go
📝 Walkthrough

Walkthrough

This PR introduces a new input.text command for literal text input and significantly expands the input.keyboard macro grammar with brace-delimited extensions. The changes add size-enforcement limits, implement a new quoted-literal and verb-form parser, introduce raw-mode parsing for non-macro text, and wire parser dispatch and serialization for both modes.

Changes

Input Text and Macro Grammar

Layer / File(s) Summary
Command definition and constraint framework
models.go, symbols.go
New ZapScriptCmdInputText constant, macro expansion limits InputMacroMaxRepeat and InputMacroMaxKeys, error variables for violations, and isInputRawCmd helper.
Input-macro grammar parsing and expansion
arguments.go
Core parser for {...} macro extensions: reads and expands braced content into tokens using quoted literals with *N repeats, verb forms (text:, delay:, press:, release:, hold:), sigil shortcuts, and key/combo forms; enforces repeat and total-key limits. Adds parseInputRawArg for input.text with literal braces and newline/tab-to-{enter}/{tab} mappings.
Parser dispatch for input-text and input-macro
parser.go
Routes commands: isInputRawCmd(name) invokes parseInputRawArg(), isInputMacroCmd(name) invokes parseInputMacroArg(), others use generic parseArgs().
Command serialization for raw and macro modes
reader.go
Command.String() dispatches on command type: input.text concatenates arguments directly without escaping; input.keyboard preserves macro-specific escaping; other commands use generic escaping/quoting.
Comprehensive test suite for grammar and round-trip
parser_input_macro_test.go, parser_fuzz_test.go, command_string_test.go
Test helpers and 11 test sections covering backward-compat, braced keys, repeats, quoted literals, verb forms, sigils, safety-cap enforcement, raw-mode semantics, error cases; fuzzing corpus expansion; command-string round-trip validation.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Possibly related PRs

  • ZaparooProject/go-zapscript#12: Modifies reader.go's Command.String() serialization and escaping/concatenation logic for input-macro commands, which this PR extends for the new raw-text mode.
  • ZaparooProject/go-zapscript#14: Modifies input-macro argument parsing in arguments.go and parseInputMacroArg, which this PR substantially overhauls with new grammar expansion and size limits.

Poem

🐰 Braces and tokens now dance in the light,
{text:hello} and {key*5} flow just right,
Raw text stays literal—no macros to parse,
Size limits protect us from expansion's vast farce,
Test cases abound, the whole system's complete! 🎉

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 30.30% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title directly and clearly summarizes the main enhancement: adding a macro language with repeat, literals, delays, holds, and raw text features.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/input-macro-language

Comment @coderabbitai help to get the list of available commands and usage tips.

@codecov

codecov Bot commented Jun 13, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 88.70056% with 20 lines in your changes missing coverage. Please review.

Files with missing lines Patch % Lines
arguments.go 89.74% 8 Missing and 8 partials ⚠️
reader.go 66.66% 4 Missing ⚠️

📢 Thoughts on this report? Let us know!

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
arguments.go (1)

135-141: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Count expression tokens toward the macro length cap.

Line 140 appends expression output but never increments totalLen, so repeated [expr] segments can bypass InputMacroMaxKeys enforcement.

Proposed fix
 		case SymExpressionStart:
 			exprValue, exprErr := sr.parseExpression()
 			if exprErr != nil {
 				return args, advArgs, exprErr
 			}
+			totalLen++
+			if totalLen > InputMacroMaxKeys {
+				return args, advArgs, ErrInputMacroTooLong
+			}
 			args = append(args, exprValue)
 			continue
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@arguments.go` around lines 135 - 141, The code handling SymExpressionStart
appends exprValue but never updates totalLen, letting repeated [expr] segments
bypass the InputMacroMaxKeys cap; after calling parseExpression and appending
exprValue to args in the SymExpressionStart case, increment totalLen by the
length of exprValue (use the same length metric used elsewhere, e.g.,
len(exprValue) or utf8.RuneCountInString if other branches use rune counts) and
perform the same InputMacroMaxKeys check/error return as other token branches so
the macro length cap is enforced for expression tokens as well.
🧹 Nitpick comments (1)
parser_input_macro_test.go (1)

50-443: 🏗️ Heavy lift

Consolidate repeated parser scenarios into table-driven tests and assert with cmp.Diff.

This file currently uses many one-off tests with assert.Equal/require.*; that conflicts with the repo’s _test.go guideline and makes scenario growth harder to maintain. Please convert repeated scenario blocks (backward-compat, repeats, literals, verbs, sigils, error-cases) into table-driven subtests and use cmp.Diff for comparison output.

As per coding guidelines, **/*_test.go: "Use table-driven tests for multiple scenarios" and "Use cmp.Diff from google/go-cmp for detailed assertion output in tests".

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@parser_input_macro_test.go` around lines 50 - 443, Many one-off tests (e.g.,
TestInputMacro_BackwardCompat_*, TestInputMacro_Repeat_*,
TestInputMacro_QuotedLiteral_*, TestInputMacro_TextVerb_*, TestInputText_*, and
the error-case tests) should be consolidated into table-driven subtests: create
slices of scenario structs (name, input, wantArgs, optionally wantErr or
wantAdvArgs) and iterate with t.Run to exercise parseInputMacroArgs,
parseInputTextArgs, or zapscript.NewParser as appropriate; replace assert.Equal
checks with a cmp.Diff comparison and fail the subtest with t.Fatalf("mismatch
(-want +got):\n%s", diff) when diff != "" (and keep
require.NoError/require.Error checks for parse errors where needed but report
mismatches with cmp.Diff), ensuring error-case scenarios assert the expected
error values (e.g., zapscript.ErrInputMacroRepeatTooLarge, ErrInputMacroTooLong,
ErrUnmatchedInputMacroExt, ErrInputMacroEmptyKey) and preserving existing
caps/adv-arg checks; group tests by logical sections (backward-compat, repeats,
quoted literals, text verb, input.text raw mode, sigils/hold verbs, safety caps,
errors) and remove duplicated one-off test functions in favor of these
table-driven subtests.

Source: Coding guidelines

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@reader.go`:
- Around line 180-186: The raw-mode branch in the
isInputRawCmd(normalizeCmdName(c.Name)) case writes args literally, which breaks
equivalence because parseInputRawArg maps newline/tab to "{enter}"/"{tab}";
change the loop that currently does for _, arg := range c.Args { _, _ =
b.WriteString(arg) } to serialize each arg using the same mapping used by
parseInputRawArg (i.e., replace '\n' with "{enter}" and '\t' with "{tab}" or
call/implement a small helper that mirrors parseInputRawArg's mapping) before
writing to b so reparsing preserves the original tokens.

---

Outside diff comments:
In `@arguments.go`:
- Around line 135-141: The code handling SymExpressionStart appends exprValue
but never updates totalLen, letting repeated [expr] segments bypass the
InputMacroMaxKeys cap; after calling parseExpression and appending exprValue to
args in the SymExpressionStart case, increment totalLen by the length of
exprValue (use the same length metric used elsewhere, e.g., len(exprValue) or
utf8.RuneCountInString if other branches use rune counts) and perform the same
InputMacroMaxKeys check/error return as other token branches so the macro length
cap is enforced for expression tokens as well.

---

Nitpick comments:
In `@parser_input_macro_test.go`:
- Around line 50-443: Many one-off tests (e.g., TestInputMacro_BackwardCompat_*,
TestInputMacro_Repeat_*, TestInputMacro_QuotedLiteral_*,
TestInputMacro_TextVerb_*, TestInputText_*, and the error-case tests) should be
consolidated into table-driven subtests: create slices of scenario structs
(name, input, wantArgs, optionally wantErr or wantAdvArgs) and iterate with
t.Run to exercise parseInputMacroArgs, parseInputTextArgs, or
zapscript.NewParser as appropriate; replace assert.Equal checks with a cmp.Diff
comparison and fail the subtest with t.Fatalf("mismatch (-want +got):\n%s",
diff) when diff != "" (and keep require.NoError/require.Error checks for parse
errors where needed but report mismatches with cmp.Diff), ensuring error-case
scenarios assert the expected error values (e.g.,
zapscript.ErrInputMacroRepeatTooLarge, ErrInputMacroTooLong,
ErrUnmatchedInputMacroExt, ErrInputMacroEmptyKey) and preserving existing
caps/adv-arg checks; group tests by logical sections (backward-compat, repeats,
quoted literals, text verb, input.text raw mode, sigils/hold verbs, safety caps,
errors) and remove duplicated one-off test functions in favor of these
table-driven subtests.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 03f59dcc-ca8c-4816-95b2-953a70c91e95

📥 Commits

Reviewing files that changed from the base of the PR and between 2380896 and 0e38c44.

📒 Files selected for processing (8)
  • arguments.go
  • command_string_test.go
  • models.go
  • parser.go
  • parser_fuzz_test.go
  • parser_input_macro_test.go
  • reader.go
  • symbols.go

Comment thread reader.go
- reader.go: reverse {enter}/{tab} mappings in Command.String() raw-mode
  serialization so round-trip parsing preserves the original tokens
- arguments.go: increment totalLen for SymExpressionStart tokens so
  expression segments cannot bypass the InputMacroMaxKeys cap
- parser_input_macro_test.go: rewrite as table-driven subtests using
  cmp.Diff following the project style; remove duplicates already covered
  by parser_coverage_test.go; add wantAnyErr for unexported error types
@wizzomafizzo wizzomafizzo merged commit 5b38094 into main Jun 13, 2026
12 checks passed
@wizzomafizzo wizzomafizzo deleted the feat/input-macro-language branch June 13, 2026 10:08
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant